1 /*
2 Date Input 1.2.1
3 Requires jQuery version: >= 1.2.6
4
5 Copyright (c) 2007-2008 Jonathan Leighton & Torchbox Ltd
6
7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use,
11 copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 OTHER DEALINGS IN THE SOFTWARE.
27 */
28
29 DateInput = (function($) {
30
31 function DateInput(el, opts) {
32 if (typeof(opts) != "object") opts = {};
33 $.extend(this, DateInput.DEFAULT_OPTS, opts);
34
35 this.input = $(el);
36 this.bindMethodsToObj("show", "hide", "hideIfClickOutside", "keydownHandler", "selectDate");
37
38 this.build();
39 this.selectDate();
40 this.hide();
41 };
42 DateInput.DEFAULT_OPTS = {
43 month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
44 short_month_names: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
45 short_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
46 start_of_week: 1
47 };
48 DateInput.prototype = {
49 build: function() {
50 var monthNav = $('<p class="month_nav">' +
51 '<span class="button prev" title="[Page-Up]">«</span>' +
52 ' <span class="month_name"></span> ' +
53 '<span class="button next" title="[Page-Down]">»</span>' +
54 '</p>');
55 this.monthNameSpan = $(".month_name", monthNav);
56 $(".prev", monthNav).click(this.bindToObj(function() { this.moveMonthBy(-1); }));
57 $(".next", monthNav).click(this.bindToObj(function() { this.moveMonthBy(1); }));
58
59 var yearNav = $('<p class="year_nav">' +
60 '<span class="button prev" title="[Ctrl+Page-Up]">«</span>' +
61 ' <span class="year_name"></span> ' +
62 '<span class="button next" title="[Ctrl+Page-Down]">»</span>' +
63 '</p>');
64 this.yearNameSpan = $(".year_name", yearNav);
65 $(".prev", yearNav).click(this.bindToObj(function() { this.moveMonthBy(-12); }));
66 $(".next", yearNav).click(this.bindToObj(function() { this.moveMonthBy(12); }));
67
68 var nav = $('<div class="nav"></div>').append(monthNav, yearNav);
69
70 var tableShell = "<table><thead><tr>";
71 $(this.adjustDays(this.short_day_names)).each(function() {
72 tableShell += "<th>" + this + "</th>";
73 });
74 tableShell += "</tr></thead><tbody></tbody></table>";
75
76 this.dateSelector = this.rootLayers = $('<div class="date_selector"></div>').append(nav, tableShell).insertAfter(this.input);
77
78 if ($.browser.msie && $.browser.version < 7) {
79
80 this.ieframe = $('<iframe class="date_selector_ieframe" frameborder="0" src="#"></iframe>').insertBefore(this.dateSelector);
81 this.rootLayers = this.rootLayers.add(this.ieframe);
82
83 $(".button", nav).mouseover(function() { $(this).addClass("hover") });
84 $(".button", nav).mouseout(function() { $(this).removeClass("hover") });
85 };
86
87 this.tbody = $("tbody", this.dateSelector);
88
89 this.input.change(this.bindToObj(function() { this.selectDate(); }));
90 this.selectDate();
91 },
92
93 selectMonth: function(date) {
94 var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);
95
96 if (!this.currentMonth || !(this.currentMonth.getFullYear() == newMonth.getFullYear() &&
97 this.currentMonth.getMonth() == newMonth.getMonth())) {
98
99 this.currentMonth = newMonth;
100
101 var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
102 var numDays = this.daysBetween(rangeStart, rangeEnd);
103 var dayCells = "";
104
105 for (var i = 0; i <= numDays; i++) {
106 var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00);
107
108 if (this.isFirstDayOfWeek(currentDay)) dayCells += "<tr>";
109
110 if (currentDay.getMonth() == date.getMonth()) {
111 dayCells += '<td class="selectable_day" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
112 } else {
113 dayCells += '<td class="unselected_month" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
114 };
115
116 if (this.isLastDayOfWeek(currentDay)) dayCells += "</tr>";
117 };
118 this.tbody.empty().append(dayCells);
119
120 this.monthNameSpan.empty().append(this.monthName(date));
121 this.yearNameSpan.empty().append(this.currentMonth.getFullYear());
122
123 $(".selectable_day", this.tbody).click(this.bindToObj(function(event) {
124 this.changeInput($(event.target).attr("date"));
125 }));
126
127 $("td[date=" + this.dateToString(new Date()) + "]", this.tbody).addClass("today");
128
129 $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover") });
130 $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover") });
131 };
132
133 $('.selected', this.tbody).removeClass("selected");
134 $('td[date=' + this.selectedDateString + ']', this.tbody).addClass("selected");
135 },
136
137 selectDate: function(date) {
138 if (typeof(date) == "undefined") {
139 date = this.stringToDate(this.input.val());
140 };
141 if (!date) date = new Date();
142
143 this.selectedDate = date;
144 this.selectedDateString = this.dateToString(this.selectedDate);
145 this.selectMonth(this.selectedDate);
146 },
147
148 changeInput: function(dateString) {
149 this.input.val(dateString).change();
150 this.hide();
151 },
152
153 show: function() {
154 this.rootLayers.css("display", "block");
155 $([window, document.body]).click(this.hideIfClickOutside);
156 this.input.unbind("focus", this.show);
157 $(document.body).keydown(this.keydownHandler);
158 this.setPosition();
159 },
160
161 hide: function() {
162 this.rootLayers.css("display", "none");
163 $([window, document.body]).unbind("click", this.hideIfClickOutside);
164 this.input.focus(this.show);
165 $(document.body).unbind("keydown", this.keydownHandler);
166 },
167
168 hideIfClickOutside: function(event) {
169 if (event.target != this.input[0] && !this.insideSelector(event)) {
170 this.hide();
171 };
172 },
173
174 insideSelector: function(event) {
175 var offset = this.dateSelector.position();
176 offset.right = offset.left + this.dateSelector.outerWidth();
177 offset.bottom = offset.top + this.dateSelector.outerHeight();
178
179 return event.pageY < offset.bottom &&
180 event.pageY > offset.top &&
181 event.pageX < offset.right &&
182 event.pageX > offset.left;
183 },
184
185 keydownHandler: function(event) {
186 switch (event.keyCode)
187 {
188 case 9:
189 case 27:
190 this.hide();
191 return;
192 break;
193 case 13:
194 this.changeInput(this.selectedDateString);
195 break;
196 case 33:
197 this.moveDateMonthBy(event.ctrlKey ? -12 : -1);
198 break;
199 case 34:
200 this.moveDateMonthBy(event.ctrlKey ? 12 : 1);
201 break;
202 case 38:
203 this.moveDateBy(-7);
204 break;
205 case 40:
206 this.moveDateBy(7);
207 break;
208 case 37:
209 this.moveDateBy(-1);
210 break;
211 case 39:
212 this.moveDateBy(1);
213 break;
214 default:
215 return;
216 }
217 event.preventDefault();
218 },
219
220 stringToDate: function(string) {
221 var matches;
222 if (matches = string.match(/^(\d{4,4})-(\d{2,2})-(\d{2,2})$/)) {
223 return new Date(matches[1], matches[2] - 1, matches[3]);
224 } else {
225 return null;
226 };
227 },
228
229
230 dateToString: function(date) {
231 // return date.getDate() + " " + this.short_month_names[date.getMonth()] + " " + date.getFullYear();
232 var month = (date.getMonth() + 1).toString();
233 var dom = date.getDate().toString();
234 if (month.length == 1) month = "0" + month;
235 if (dom.length == 1) dom = "0" + dom;
236 return dom + "-" + month + "-" +date.getFullYear() ;
237
238 },
239
240 setPosition: function() {
241 var offset = this.input.offset();
242 this.rootLayers.css({
243 top: offset.top + this.input.outerHeight(),
244 left: offset.left
245 });
246
247 if (this.ieframe) {
248 this.ieframe.css({
249 width: this.dateSelector.outerWidth(),
250 height: this.dateSelector.outerHeight()
251 });
252 };
253 },
254
255 moveDateBy: function(amount) {
256 var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() + amount);
257 this.selectDate(newDate);
258 },
259
260 moveDateMonthBy: function(amount) {
261 var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth() + amount, this.selectedDate.getDate());
262 if (newDate.getMonth() == this.selectedDate.getMonth() + amount + 1) {
263
264 newDate.setDate(0);
265 };
266 this.selectDate(newDate);
267 },
268
269 moveMonthBy: function(amount) {
270 var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount, this.currentMonth.getDate());
271 this.selectMonth(newMonth);
272 },
273
274 monthName: function(date) {
275 return this.month_names[date.getMonth()];
276 },
277
278 bindToObj: function(fn) {
279 var self = this;
280 return function() { return fn.apply(self, arguments) };
281 },
282
283 bindMethodsToObj: function() {
284 for (var i = 0; i < arguments.length; i++) {
285 this[arguments[i]] = this.bindToObj(this[arguments[i]]);
286 };
287 },
288
289 indexFor: function(array, value) {
290 for (var i = 0; i < array.length; i++) {
291 if (value == array[i]) return i;
292 };
293 },
294
295 monthNum: function(month_name) {
296 return this.indexFor(this.month_names, month_name);
297 },
298
299 shortMonthNum: function(month_name) {
300 return this.indexFor(this.short_month_names, month_name);
301 },
302
303 shortDayNum: function(day_name) {
304 return this.indexFor(this.short_day_names, day_name);
305 },
306
307 daysBetween: function(start, end) {
308 start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
309 end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
310 return (end - start) / 86400000;
311 },
312
313 changeDayTo: function(dayOfWeek, date, direction) {
314 var difference = direction * (Math.abs(date.getDay() - dayOfWeek - (direction * 7)) % 7);
315 return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference);
316 },
317
318 rangeStart: function(date) {
319 return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1);
320 },
321
322 rangeEnd: function(date) {
323 return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1);
324 },
325
326 isFirstDayOfWeek: function(date) {
327 return date.getDay() == this.start_of_week;
328 },
329
330 isLastDayOfWeek: function(date) {
331 return date.getDay() == (this.start_of_week - 1) % 7;
332 },
333
334 adjustDays: function(days) {
335 var newDays = [];
336 for (var i = 0; i < days.length; i++) {
337 newDays[i] = days[(i + this.start_of_week) % 7];
338 };
339 return newDays;
340 }
341 };
342
343 $.fn.date_input = function(opts) {
344 return this.each(function() { new DateInput(this, opts); });
345 };
346 $.date_input = { initialize: function(opts) {
347 $("input.date_input").date_input(opts);
348
349 } };
350
351 return DateInput;
352 })(jQuery);